#include "EventHub.hpp"
#include <libtimers.h>
#include <ext/hash_map>
#include <sys/epoll.h>
#include <cerrno>


namespace tcplib { 


EventHub::EventHub()
    :_is_running(false)
{
    /* argument for epoll_create is obsoleted */
    _epoll_fd = epoll_create(128);
    if (_epoll_fd < 0)
        panic("epoll_create failed");

    _timer_pool = lt_new(NULL);
}

EventHub::~EventHub()
{
    closefd(_epoll_fd);
    lt_destroy((LT_TIMERPOOL*)(_timer_pool));
}


int EventHub::add_io_event(int fd, int event, void (*handler)(void *, int), void *obj)
{
    bool new_event = false;
    __gnu_cxx::hash_map<int, IOCallback>::iterator it = _io_cbmap.find(fd);

    if (it == _io_cbmap.end()) {
        it = _io_cbmap.insert(std::make_pair(fd, IOCallback())).first;
        new_event = true;
    }
    
    IOCallback &cbs = it->second;

    if (event & READABLE) {
        if (cbs.readcb == NULL) cbs.count++;
        cbs.readcb = handler;
        cbs.readarg = obj;
    } else if (event & WRITABLE) {
        if (cbs.writecb == NULL) cbs.count++;
        cbs.writecb = handler;
        cbs.writearg = obj;
    } else if (event & ERROR) {
        if (cbs.errorcb == NULL) cbs.count++;
        cbs.errorcb = handler;
        cbs.errorarg = obj;
    }

    struct epoll_event s_event;
    s_event.data.fd = fd;
    s_event.events = 0;
    if (cbs.readcb) s_event.events |= EPOLLIN;
    if (cbs.writecb) s_event.events |= EPOLLOUT;
    return epoll_ctl(_epoll_fd, new_event? EPOLL_CTL_ADD: EPOLL_CTL_MOD, fd, &s_event);

}


int EventHub::remove_io_event(int fd, int event)
{
    __gnu_cxx::hash_map<int, IOCallback>::iterator it = _io_cbmap.find(fd);
    if (it == _io_cbmap.end())
        return -1;

    IOCallback &cbs = it->second;

    if ((event & READABLE) && (cbs.readcb)) {
        cbs.count--;
        cbs.readcb = NULL;
    }

    if ((event & WRITABLE) && (cbs.writecb)) {
        cbs.count--;
        cbs.writecb = NULL;
    }

    if ((event & ERROR) && (cbs.errorcb)) {
        cbs.count--;
        cbs.errorcb = NULL;
    }

    struct epoll_event s_event;
    s_event.data.fd = fd;
    s_event.events = 0;
    if (cbs.readcb) s_event.events |= EPOLLIN;
    if (cbs.writecb) s_event.events |= EPOLLOUT;
    if (cbs.count == 0) {
        _io_cbmap.erase(it);
        return epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, NULL);
    }
    return epoll_ctl(_epoll_fd, EPOLL_CTL_MOD, fd, &s_event);
}

uint32_t EventHub::open_timer()
{
    return lt_timer_open((LT_TIMERPOOL*)_timer_pool);
}

int EventHub::set_timer(uint32_t tfd, uint32_t expire_ms, void (*handler)(void *, uint32_t), void *obj)
{
    TimerCallback cb;
    cb.cb = handler;
    cb.arg = obj;
    _timer_cbmap.insert(std::make_pair(tfd, cb));
    return lt_set_timer((LT_TIMERPOOL*)_timer_pool, tfd, NULL, expire_ms);
}

int EventHub::cancel_timer(uint32_t tfd)
{
    return lt_cancel_timer((LT_TIMERPOOL*)_timer_pool, tfd);
}

int EventHub::close_timer(uint32_t tfd)
{
    _timer_cbmap.erase(tfd);
    return lt_timer_close((LT_TIMERPOOL*)_timer_pool, tfd);
}

void EventHub::run()
{
    static const int EVENT_SIZE = 4096;
    struct epoll_event *event_list = new struct epoll_event[EVENT_SIZE];

    _is_running = true;
    for(;_is_running;) {
        int ret = epoll_wait(_epoll_fd, event_list, EVENT_SIZE, 10);
        if (ret < 0) {
            if (errno == EINTR) continue;
            else 
                panic("epoll_wait return unexpected error");
        }

        for(int i = 0; i < ret; i++) {
            int fd = event_list[i].data.fd;
            __gnu_cxx::hash_map<int, IOCallback>::iterator it = _io_cbmap.find(fd);
            if (it == _io_cbmap.end())
                continue;
            IOCallback &cbs = it->second;
            if ((event_list[i].events & EPOLLIN)
                    && cbs.readcb) 
                cbs.readcb(cbs.readarg, fd);
            if ((event_list[i].events & EPOLLOUT)
                    && cbs.writecb) 
                cbs.writecb(cbs.writearg, fd);
            if (((event_list[i].events & EPOLLHUP)
                    || (event_list[i].events & EPOLLERR))
                    && cbs.errorcb) 
                cbs.errorcb(cbs.errorarg, fd);
        }

        LT_TIMER_EVENT *timer_evlist;
        ret = lt_poll((LT_TIMERPOOL*)_timer_pool, &timer_evlist);
        for(int i = 0; i < ret; i++) {
            uint32_t tfd = timer_evlist[i].timer_id;
            __gnu_cxx::hash_map<uint32_t, TimerCallback>::iterator it = _timer_cbmap.find(tfd);
            if (it == _timer_cbmap.end())
                continue;
            TimerCallback &cb = it->second;
            cb.cb(cb.arg, tfd);
        }
        lt_free_events((LT_TIMERPOOL*)_timer_pool, timer_evlist);
    }

    delete[] event_list;
}

void EventHub::stop()
{
    _is_running = false;
}

};


